from fastapi import FastAPI, Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse, JSONResponse
from fastapi.exceptions import RequestValidationError
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime, timedelta
import logging
from sqlalchemy.exc import SQLAlchemyError, IntegrityError

from app.db import get_db, init_db
from app.models.models import Student, Admin, Room, Preference
from app.services.roommate_matching import RoommateMatchingService
from app.services.admin_service import AdminService
from app.routers import admin
from app.schemas import (
    StudentCreate, 
    StudentResponse, 
    StudentUpdate,
    PreferenceCreate,
    MatchingResponse,
    Token
)
from app.auth import create_access_token, get_password_hash, get_current_user
from app.init_data import init_test_data

app = FastAPI(title="宿舍管理系统")

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 请求验证错误处理
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    errors = []
    for error in exc.errors():
        field = error["loc"][-1]
        if error["type"] == "type_error.enum":
            msg = f"请选择有效的{field}值"
        elif error["type"] == "value_error.missing":
            msg = f"请填写{field}"
        elif error["type"] == "value_error.email":
            msg = "请输入有效的邮箱地址"
        else:
            msg = error["msg"]
        errors.append(msg)
    
    return JSONResponse(
        status_code=400,
        content=errors[0] if errors else "输入数据验证失败"
    )

# 自定义错误处理
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    return JSONResponse(
        status_code=exc.status_code,
        content=exc.detail
    )

@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content=str(exc)
    )

# CORS配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 挂载静态文件目录
app.mount("/static", StaticFiles(directory="app/static"), name="static")

# 包含管理员路由
app.include_router(admin.router)

# 初始化数据库
@app.on_event("startup")
async def startup_event():
    init_db()
    # 初始化测试数据
    db = next(get_db())
    try:
        init_test_data(db)
    except Exception as e:
        logger.error(f"初始化测试数据失败: {str(e)}")
    finally:
        db.close()

@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
    # 验证学生
    student = db.query(Student).filter(Student.student_id == form_data.username).first()
    if student and student.hashed_password == get_password_hash(form_data.password):
        token = create_access_token(
            data={"sub": student.student_id},
            expires_delta=timedelta(minutes=30)
        )
        return Token(access_token=token, token_type="bearer")
    
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="用户名或密码错误",
        headers={"WWW-Authenticate": "Bearer"},
    )

@app.post("/students/", response_model=StudentResponse)
async def create_student(student: StudentCreate, db: Session = Depends(get_db)):
    """创建新学生账户"""
    try:
        logger.info("=== 开始创建学生账户 ===")
        logger.info(f"接收到的数据: {student.dict(exclude={'password'})}")
        
        # 检查学号是否已存在
        existing_student = db.query(Student).filter(Student.student_id == student.student_id).first()
        if existing_student:
            logger.warning(f"学号 {student.student_id} 已存在")
            return JSONResponse(
                status_code=400,
                content={"message": "该学号已被注册"}
            )
            
        # 检查邮箱是否已存在
        existing_email = db.query(Student).filter(Student.email == student.email).first()
        if existing_email:
            logger.warning(f"邮箱 {student.email} 已存在")
            return JSONResponse(
                status_code=400,
                content={"message": "该邮箱已被注册"}
            )
            
        logger.info("数据验证通过，开始创建账户")
        
        try:
            # 创建新学生
            db_student = Student(
                student_id=student.student_id,
                name=student.name,
                email=student.email,
                gender=student.gender,
                hashed_password=get_password_hash(student.password),
                major=student.major,
                is_active=True
            )
            
            logger.info(f"准备保存的学生对象: {db_student.__dict__}")
            
            # 尝试添加到数据库
            try:
                db.add(db_student)
                logger.info("已添加到数据库会话")
                db.flush()
                logger.info("数据库刷新成功")
                db.commit()
                logger.info("数据库提交成功")
                db.refresh(db_student)
                logger.info("数据库刷新成功")
                
            except IntegrityError as ie:
                db.rollback()
                logger.error(f"数据完整性错误: {str(ie)}")
                return JSONResponse(
                    status_code=400,
                    content={"message": "数据验证失败，请检查输入信息"}
                )
                
            except SQLAlchemyError as se:
                db.rollback()
                logger.error(f"数据库错误: {str(se)}")
                return JSONResponse(
                    status_code=500,
                    content={"message": "数据库操作失败，请稍后重试"}
                )
            
            logger.info("账户创建成功")
            
            # 转换为响应数据
            response_data = {
                "id": db_student.id,
                "student_id": db_student.student_id,
                "name": db_student.name,
                "email": db_student.email,
                "gender": db_student.gender.value if db_student.gender else None,
                "major": db_student.major,
                "is_active": db_student.is_active,
                "created_at": db_student.created_at.isoformat() if db_student.created_at else None
            }
            
            return JSONResponse(
                status_code=200,
                content={
                    "message": "注册成功",
                    "data": response_data
                }
            )
            
        except Exception as e:
            logger.error(f"创建学生对象失败: {str(e)}", exc_info=True)
            return JSONResponse(
                status_code=500,
                content={"message": "创建账户失败，请稍后重试"}
            )
            
    except Exception as e:
        logger.error(f"发生未知错误: {str(e)}", exc_info=True)
        return JSONResponse(
            status_code=500,
            content={"message": "系统错误，请稍后重试"}
        )
    finally:
        logger.info("=== 创建学生账户结束 ===\n")

@app.get("/students/me/matches", response_model=List[MatchingResponse])
async def get_current_student_matches(
    min_score: float = 60.0,
    top_n: int = 5,
    current_user: Student = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """获取当前登录学生的室友匹配结果"""
    try:
        print(f"\n=== 开始匹配过程 ===")
        print(f"当前用户: ID={current_user.id}, student_id={current_user.student_id}, name={current_user.name}")
        print(f"基本信息: 性别={current_user.gender}, 专业={current_user.major}, 年级={current_user.grade}")
        print(f"习惯信息: 作息={current_user.sleeping_habit}, 卫生={current_user.cleanliness}")
        
        # 检查基本信息是否完整
        if not current_user.major or not current_user.grade:
            raise HTTPException(
                status_code=400,
                detail="请先完善个人基本信息（专业和年级）"
            )
        
        # 检查是否设置了基本偏好
        if not current_user.sleeping_habit or not current_user.cleanliness:
            raise HTTPException(
                status_code=400,
                detail="请先设置作息时间和卫生习惯"
            )
            
        # 检查是否设置了兴趣标签
        preference = db.query(Preference).filter(Preference.student_id == current_user.id).first()
        print(f"偏好设置: {preference}")
        
        if not preference:
            raise HTTPException(
                status_code=400,
                detail="请先设置个人偏好"
            )
            
        if not preference.interest_tags or len(preference.interest_tags) == 0:
            raise HTTPException(
                status_code=400,
                detail="请至少选择一个兴趣标签"
            )
            
        print("所有检查通过，开始寻找匹配...")
        matcher = RoommateMatchingService(db)
        matches = matcher.get_top_matches(current_user, top_n)
        print(f"找到 {len(matches)} 个匹配结果")
        
        if not matches:
            print("没有找到合适的匹配")
            return []
            
        print("=== 匹配过程结束 ===\n")
        return matches
        
    except HTTPException as he:
        print(f"HTTP异常: {he.detail}")
        raise he
    except Exception as e:
        print(f"发生未知错误: {str(e)}")
        print(f"错误类型: {type(e)}")
        import traceback
        print(f"错误堆栈: {traceback.format_exc()}")
        raise HTTPException(
            status_code=500,
            detail=f"获取匹配结果时发生错误: {str(e)}"
        )

@app.get("/rooms/{room_id}/students", response_model=List[StudentResponse])
async def get_room_students(
    room_id: int,
    db: Session = Depends(get_db)
):
    room = db.query(Room).filter(Room.id == room_id).first()
    if not room:
        raise HTTPException(status_code=404, detail="房间不存在")
    
    return [assignment.student for assignment in room.assignments if assignment.is_active]

@app.get("/")
async def read_root():
    return RedirectResponse(url="/static/index.html")

@app.get("/students/me", response_model=StudentResponse)
async def get_current_student(
    current_user: Student = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """获取当前登录学生的信息"""
    try:
        logger.info(f"获取用户信息: ID={current_user.id}, student_id={current_user.student_id}")
        
        # 刷新数据库中的用户信息
        db.refresh(current_user)
        
        # 构造响应数据
        response_data = {
            "id": current_user.id,
            "student_id": current_user.student_id,
            "name": current_user.name,
            "email": current_user.email,
            "gender": current_user.gender.value if current_user.gender else None,
            "major": current_user.major,
            "is_active": current_user.is_active,
            "created_at": current_user.created_at.isoformat() if current_user.created_at else None,
            "room_id": current_user.room_id
        }
        
        logger.info(f"返回用户信息: {response_data}")
        return JSONResponse(
            status_code=200,
            content={
                "message": "获取用户信息成功",
                "data": response_data
            }
        )
        
    except Exception as e:
        logger.error(f"获取用户信息失败: {str(e)}", exc_info=True)
        return JSONResponse(
            status_code=500,
            content={"message": "获取用户信息失败，请稍后重试"}
        )

@app.put("/students/me", response_model=StudentResponse)
async def update_current_student(
    student_update: StudentUpdate,
    current_user: Student = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """更新当前登录学生的信息"""
    try:
        logger.info(f"开始更新用户信息: ID={current_user.id}, student_id={current_user.student_id}")
        logger.info(f"更新数据: {student_update.dict(exclude_unset=True)}")
        
        # 获取要更新的字段
        update_data = student_update.dict(exclude_unset=True)
        
        if not update_data:
            logger.warning("没有需要更新的数据")
            return JSONResponse(
                status_code=400,
                content={"message": "没有需要更新的数据"}
            )
            
        try:
            # 更新用户信息
            for key, value in update_data.items():
                setattr(current_user, key, value)
            
            logger.info("正在保存更新...")
            db.commit()
            db.refresh(current_user)
            logger.info("更新成功")
            
            # 构造响应数据
            response_data = {
                "id": current_user.id,
                "student_id": current_user.student_id,
                "name": current_user.name,
                "email": current_user.email,
                "gender": current_user.gender.value if current_user.gender else None,
                "major": current_user.major,
                "grade": current_user.grade,
                "is_active": current_user.is_active,
                "created_at": current_user.created_at.isoformat() if current_user.created_at else None,
                "room_id": current_user.room_id
            }
            
            return JSONResponse(
                status_code=200,
                content={
                    "message": "个人信息更新成功",
                    "data": response_data
                }
            )
            
        except Exception as db_error:
            db.rollback()
            logger.error(f"数据库更新失败: {str(db_error)}", exc_info=True)
            return JSONResponse(
                status_code=500,
                content={"message": "数据库更新失败，请稍后重试"}
            )
            
    except Exception as e:
        logger.error(f"更新用户信息时发生错误: {str(e)}", exc_info=True)
        return JSONResponse(
            status_code=500,
            content={"message": "系统错误，请稍后重试"}
        )

@app.post("/students/me/preferences")
async def set_current_student_preferences(
    preferences: PreferenceCreate,
    current_user: Student = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """设置当前登录学生的偏好"""
    try:
        logger.info(f"开始更新用户偏好: ID={current_user.id}, student_id={current_user.student_id}")
        logger.info(f"偏好数据: {preferences.dict()}")
        
        # 验证输入数据
        if not preferences.sleeping_habit or not preferences.cleanliness:
            logger.warning("作息习惯和卫生习惯不能为空")
            return JSONResponse(
                status_code=400,
                content={"message": "作息习惯和卫生习惯不能为空"}
            )
            
        if not preferences.interest_tags or len(preferences.interest_tags) == 0:
            logger.warning("请至少选择一个兴趣标签")
            return JSONResponse(
                status_code=400,
                content={"message": "请至少选择一个兴趣标签"}
            )
            
        if len(preferences.interest_tags) > 8:
            logger.warning("兴趣标签不能超过8个")
            return JSONResponse(
                status_code=400,
                content={"message": "兴趣标签不能超过8个"}
            )
            
        try:
            # 更新学生的基本偏好
            logger.info("更新基本偏好...")
            current_user.sleeping_habit = preferences.sleeping_habit
            current_user.cleanliness = preferences.cleanliness
            
            # 更新或创建偏好记录
            preference = db.query(Preference).filter(Preference.student_id == current_user.id).first()
            if not preference:
                logger.info("创建新的偏好记录")
                preference = Preference(
                    student_id=current_user.id,
                    interest_tags=preferences.interest_tags
                )
                db.add(preference)
            else:
                logger.info("更新现有偏好记录")
                preference.interest_tags = preferences.interest_tags
            
            # 提交更改
            try:
                db.commit()
                db.refresh(current_user)
                db.refresh(preference)
                logger.info("偏好设置更新成功")
                
                # 构造响应数据
                response_data = {
                    "sleeping_habit": current_user.sleeping_habit.value,
                    "cleanliness": current_user.cleanliness.value,
                    "interest_tags": preference.interest_tags
                }
                
                return JSONResponse(
                    status_code=200,
                    content={
                        "message": "偏好设置已更新",
                        "data": response_data
                    }
                )
                
            except Exception as commit_error:
                db.rollback()
                logger.error(f"提交更改失败: {str(commit_error)}", exc_info=True)
                return JSONResponse(
                    status_code=500,
                    content={"message": "保存偏好设置失败，请稍后重试"}
                )
            
        except Exception as db_error:
            db.rollback()
            logger.error(f"数据库操作失败: {str(db_error)}", exc_info=True)
            return JSONResponse(
                status_code=500,
                content={"message": "保存偏好设置失败，请稍后重试"}
            )
            
    except Exception as e:
        logger.error(f"更新偏好设置时发生错误: {str(e)}", exc_info=True)
        return JSONResponse(
            status_code=500,
            content={"message": "系统错误，请稍后重试"}
        )

@app.get("/students/me/preferences")
async def get_current_student_preferences(
    current_user: Student = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """获取当前登录学生的偏好设置"""
    try:
        print(f"Getting preferences for student: ID={current_user.id}, student_id={current_user.student_id}")
        
        # 获取偏好设置
        preference = db.query(Preference).filter(Preference.student_id == current_user.id).first()
        
        # 如果没有偏好记录，返回空的默认值
        if not preference:
            return {
                "sleeping_habit": current_user.sleeping_habit.value if current_user.sleeping_habit else None,
                "cleanliness": current_user.cleanliness.value if current_user.cleanliness else None,
                "interest_tags": []
            }
            
        return {
            "sleeping_habit": current_user.sleeping_habit.value if current_user.sleeping_habit else None,
            "cleanliness": current_user.cleanliness.value if current_user.cleanliness else None,
            "interest_tags": preference.interest_tags or []
        }
    except Exception as e:
        print(f"Error getting preferences: {str(e)}")
        raise HTTPException(
            status_code=400,
            detail=f"获取偏好设置失败: {str(e)}"
        )

@app.post("/students/me/roommate-requests/{target_student_id}")
async def send_roommate_request(
    target_student_id: str,
    current_user: Student = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    """发送室友申请"""
    try:
        print(f"Request - Starting roommate request from student: ID={current_user.id} to student_id={target_student_id}")
        
        # 检查目标学生是否存在
        target_student = db.query(Student).filter(Student.student_id == target_student_id).first()
        if not target_student:
            raise HTTPException(status_code=404, detail="目标学生不存在")
            
        # 检查是否已经发送过申请
        existing_pref = db.query(Preference).filter(
            Preference.student_id == current_user.id,
            Preference.preferred_student_id == target_student.id
        ).first()
        
        if existing_pref:
            print(f"Request - Updating existing preference: {existing_pref.id}")
            existing_pref.preference_level = 5  # 最高偏好级别
            existing_pref.created_at = datetime.utcnow()
        else:
            print(f"Request - Creating new preference")
            new_pref = Preference(
                student_id=current_user.id,
                preferred_student_id=target_student.id,
                preference_level=5,  # 最高偏好级别
                created_at=datetime.utcnow()
            )
            db.add(new_pref)
        
        db.commit()
        print("Request - Successfully sent roommate request")
        
        return {"message": "室友申请已发送"}
    except Exception as e:
        db.rollback()
        print(f"Request - Error occurred: {str(e)}")
        if isinstance(e, HTTPException):
            raise e
        raise HTTPException(
            status_code=400,
            detail=f"发送室友申请失败: {str(e)}"
        )

# 更多API端点... 